package edu.tongji.structureplayer.ui
{
    
    import edu.tongji.structureplayer.TheAppClassic;
    import edu.tongji.structureplayer.control.ControllerI;
    import edu.tongji.structureplayer.core.ErrorNames;
    import edu.tongji.structureplayer.structure.Node;
    import edu.tongji.structureplayer.structure.StructureNames;
    import edu.tongji.structureplayer.structure.WellBehavedStructure;
    
    import flash.events.Event;
    import flash.events.KeyboardEvent;
    import flash.filesystem.File;
    import flash.filesystem.FileMode;
    import flash.filesystem.FileStream;
    
    import flashx.textLayout.elements.ParagraphElement;
    import flashx.textLayout.elements.SpanElement;
    import flashx.textLayout.elements.TextFlow;
    
    import mx.controls.Alert;
    
    import org.as3commons.collections.LinkedList;
    import org.as3commons.collections.framework.ILinkedList;
    import org.as3commons.collections.framework.ILinkedListIterator;
    import org.as3commons.collections.framework.core.LinkedListIterator;
    import org.as3commons.lang.StringBuffer;
    import org.springextensions.actionscript.context.support.FlexXMLApplicationContext;
    
    import spark.components.RichEditableText;
    import spark.events.TextOperationEvent;
    
    /**
    * Represents the interpreter who transforms user's commands into WellBehavedStructure.
    * 
    * <p>Only several commands can be accepted by the interpreter. They are,
    * <ul>
    *   <li>Node name(x, y)</li>
    *   <li>Function name(x1, y1, x2, y2, ...)</li>
    *   <li>Connection name(xEnable, yEnable, thetaEnable)</li>
    *   <li>Connection name(xEnable, yEnable, thetaEnable, Function)</li>
    *   <li>MaterialProperty name(elasticModulus, posissonRatio)</li>
    *   <li>SectionConstant name(crossArea, secondMomentOfArea)</li>
    *   <li>Property name(MaterialProperty, SectionConstant)</li>
    *   <li>Element name(Node, Node, Property, Connection, Connection)</li>
    *   <li>Constrain name(Node, Connection, theta)</li>
    *   <li>KeyPoint name(position, strength, direction)</li>
    *   <li>Load name(Node, strength, direction, Type)</li>
    *   <li>Load name(Element, KeyPoint, KeyPoint, Type)</li>
    * </ul>
    * </p>
    */
    public class Interpreter extends RichEditableText
    {
        private var _applicationContext:FlexXMLApplicationContext;
        
        private var _structure:WellBehavedStructure;
        private var controller:ControllerI;
        
        private var testReg:RegExp;
        private var connectionReg:RegExp;
        private var elementReg:RegExp;
        private var materialPropertyReg:RegExp;
        private var nodeReg:RegExp;
        private var functionReg:RegExp;
        private var propertyReg:RegExp;
        private var sectionConstantReg:RegExp;
        private var loadReg:RegExp;
        private var keyPointReg:RegExp;
        private var constrainReg:RegExp;
        
        private var importXMLReg:RegExp;
        private var exportXMLReg:RegExp;
        private var saveReg:RegExp;
        private var openReg:RegExp;
        
        public function Interpreter()
        {
            super();
            this.addEventListener(TextOperationEvent.CHANGE, textChangeHandler);
            /*_commands = new LinkedList();
            // Add the first line that is empty.
            _commands.add("");
            _structure = new WellBehavedStructure();*/
            initRegExp();
            this.addEventListener(KeyboardEvent.KEY_DOWN, myKeyDownHandler);
        }
        
        public function get applicationContext():FlexXMLApplicationContext
        {
            return _applicationContext;
        }

        public function set applicationContext(value:FlexXMLApplicationContext):void
        {
            _applicationContext = value;
            controller = _applicationContext.getObject("controller")
                as ControllerI;
        }

        private function myKeyDownHandler(event:KeyboardEvent):void
        {
            // TODO
            // This fix a bug that occurs when deleting text in RichEditableText
            // by pressing and holding key `backspace' under one pre-condition
            // that the text color will be changed in change-event handler of the
            // RichEditableText. But I need to figure out why. 
            event.updateAfterEvent();
        }
        
        public function textChangeHandler(event:TextOperationEvent):void 
        {
            var lines:Array = this.text.split('\n');
            /*createTextFlow(lines);*/
            /*arrangeCommands(lines);*/
            createStructure(lines);
            
        }
        
        private function initRegExp():void
        {
            var float:String = "[+-]?([1-9][0-9]*|0)(\.[0-9]+)?";
            var word:String = "[a-zA-Z_][a-zA-Z_0-9]*";
            var bool:String = "(true|false|1|0)";
            var spaceStar:String = "[ ]*";
            var spacePlus:String = "[ ]+";
            var leftBrace:String = "[ ]*\\([ ]*";
            var rightBrace:String = "[ ]*\\)[ ]*";
            var comma:String = "[ ]*,[ ]*";
            testReg = new RegExp("^((\.[0-9]+)?,(\.[0-9]+)?,)*$");//"("+float+","+float+")$");
            //testReg = new RegExp("^(1(\.[0-9]+)?,2(\.[0-9]+)?,)*(3(\.[0-9]+)?,4(\.[0-9]+)?)$");
            
            
            nodeReg = new RegExp("^" + spaceStar + StructureNames.NODE + spacePlus 
                + word + leftBrace + float + comma + float + rightBrace + "$");
            materialPropertyReg = new RegExp("^" + spaceStar + StructureNames.MATERIALPROPERTY
                + spacePlus + word + leftBrace + float + comma + float + rightBrace + "$");
            sectionConstantReg = new RegExp("^" + spaceStar + StructureNames.SECTIONCONSTANT
                + spacePlus + word + leftBrace + float + comma + float + rightBrace + "$");
            propertyReg = new RegExp("^" + spaceStar + StructureNames.PROPERTY
                + spacePlus + word + leftBrace + word + comma + word + rightBrace + "$");
            functionReg = new RegExp("^" + spaceStar + StructureNames.FUNCTION
                + spacePlus + word + leftBrace 
                // This is bug. I can not fix it now.
                + "(" + float + comma + ")*"
                + "(" + float + ")"
                //+ "((" + float + comma + float + "){1}"
                //+ "(" + comma + float + comma + float + ")*)"
                + rightBrace + "$");
            connectionReg = new RegExp("^" + spaceStar + StructureNames.CONNECTION 
                + spacePlus + word + leftBrace
                + bool + comma + bool + comma + bool + "(" + comma + word + ")?"
                + rightBrace + "$");
            elementReg = new RegExp("^" + spaceStar + StructureNames.ELEMENT
                + spacePlus + word + leftBrace
                + word + "(" + comma + word + "){4}"
                + rightBrace + "$");
            constrainReg = new RegExp("^" + spaceStar + StructureNames.CONSTRAIN
                + spacePlus + word + leftBrace + word + comma + word + comma + float
                + rightBrace + "$");
            keyPointReg = new RegExp("^" + spaceStar + StructureNames.KEYPOINT
                + spacePlus + word + leftBrace + float + comma + float + comma 
                + float + rightBrace + "$");
            loadReg = new RegExp("^" + spaceStar + StructureNames.LOAD
                + spacePlus + word + leftBrace + word + comma 
                + "((" + word + comma + word + ")|(" + float + comma + float + "))" 
                + comma + "(Linear|Moment|L|M|l|m)" 
                + rightBrace + "$");
            exportXMLReg = new RegExp("^" + spaceStar + "Export" + spaceStar + "$");
            saveReg = new RegExp("^" + spaceStar + "Save" + spaceStar + "$");
            openReg = new RegExp("^" + spaceStar + "Open" + spaceStar + "$");
        }
        
        private function createStructure(lines:Array):void
        {
            _structure = new WellBehavedStructure();
            for (var i:uint=0; i<lines.length; i++)
                parseLine(i);
            _structure.debugPrint();
        }
        
        private function parseLine(lineNum:uint):void
        {
            var p:ParagraphElement = this.textFlow.getChildAt(lineNum) as ParagraphElement;
            if (p == null)return;
            var s:SpanElement = p.getChildAt(0) as SpanElement;
            var pass:Boolean = true;
            var result:Array = trimAndCompact(s.text);
            try
            {
                if (nodeReg.test(s.text))
                    _structure.addNode(result[1], result[2], result[3]);
                else if (materialPropertyReg.test(s.text))
                    _structure.addMaterialProperty(result[1], result[2], result[3]);
                else if (sectionConstantReg.test(s.text))
                    _structure.addSectionConstant(result[1], result[2], result[3]);
                else if (propertyReg.test(s.text))
                    _structure.addProperty(result[1], result[2], result[3]);
                else if (functionReg.test(s.text))
                {
                    var xPos:LinkedList = new LinkedList();
                    var yPos:LinkedList = new LinkedList();
                    var index:uint = 2;
                    while (index+1<result.length)
                    {
                        xPos.addLast(new Number(result[index++]));
                        yPos.addLast(new Number(result[index++]));
                    }
                    _structure.addPiecewiseFunction(result[1], xPos.toArray(), yPos.toArray());
                }
                else if (connectionReg.test(s.text))
                {
                    // situation: cn c1(1,1,1)
                    if (result.length==5)
                        _structure.addConnection(result[1], result[2], result[3], result[4]);
                    // situation: cn c2(1,1,1,func1)
                    else
                        _structure.addConnection(result[1], result[2], result[3], result[4], result[5]);
                }
                else if (elementReg.test(s.text))
                    _structure.addElement(result[1], result[2], result[3], 
                        result[4], result[5], result[6]);
                else if (constrainReg.test(s.text))
                    _structure.addConstrain(result[1], result[2], result[3], result[4]); 
                else if (loadReg.test(s.text))
                {
                    if ((new RegExp(StructureNames.NODE)).test(result[2]))
                        _structure.addLoadOnNode(result[1], result[2], result[3], result[4], result[5]);
                    else
                        _structure.addLoadOnElement(result[1], result[2], result[3], result[4], result[5]);
                }
                else if (keyPointReg.test(s.text))
                    _structure.addKeyPoint(result[1], result[2], result[3], result[4]);
                else if (exportXMLReg.test(s.text))
                {
                    if (textFlow.numChildren == lineNum+2)
                    {
                        var docsDir:File = File.documentsDirectory;
                        docsDir.browseForSave("Export Structure");
                        docsDir.addEventListener(Event.SELECT, exportFile);
                        // removes line `Export\n' which has 7 chars.
                        text = text.substr(0, text.length-8);
                    }
                }
                else if (saveReg.test(s.text))
                {
                    if (textFlow.numChildren == lineNum+2)
                    {
                        // removes line `Save\n' which has 5 chars.
                        text = text.substr(0, text.length-6);
                        
                        var saveDir:File = File.documentsDirectory;
                        saveDir.browseForSave("Save Structure");
                        saveDir.addEventListener(Event.SELECT, saveFile);
                    }
                }
                else if (openReg.test(s.text))
                {
                    if (textFlow.numChildren == lineNum+2)
                    {
                        // removes line `Open\n' which has 5 chars.
                        text = text.substr(0, text.length-6);
                        
                        var openDir:File = File.documentsDirectory;
                        openDir.browseForOpen("Open Structure");
                        openDir.addEventListener(Event.SELECT, openFile);
                    }
                }
                else if (testReg.test(s.text)){}
                else
                    pass = false;
            } catch (error:Error) {
                pass = false;
                Alert.show(error.message);
            }
            if (pass)
                s.setStyle("color", 0x00ff00);
            else
                s.setStyle("color", 0xff0000);
        }
        
        private function exportFile(event:Event):void 
        {
            var file:File = event.target as File;
            controller.exportXML(_structure, file);
        }
        
        private function saveFile(event:Event):void
        {
            var file:File = event.target as File;
            controller.save(text, file);
        }
        
        private function openFile(event:Event):void
        {
            var file:File = event.target as File;
            text = controller.open(file);
        }
        /*private function arrangeCommands(lines:Array):void
        {
            var i:uint = 0;
            var lli:LinkedListIterator = _commands.iterator() as LinkedListIterator;
            while (lli.hasNext())
            {
                lli.next();
                if (lli.current != lines[i])
                    break;
                i++;
            }
            
            if (_commands.size < lines.length)
                lli.addAfter(new String(lines[i]));
            else if (_commands.size > lines.length)
                lli.remove();
            else
            {
                if (parseLine(lli.current, i))
                    lli.replace(new String(lines[i]));
                else
                    lli.replace("");
            }
            
            debugPrintCommands();
            _structure.debugPrint();
        }*/
        
        /*private function countValidLines(name:String, index:uint):uint
        {
            var regExp:RegExp;
            switch (name)
            {
                case StructureNames.NODE: regExp = nodeReg;break;
                default: regExp = testReg;break;
            }
            var count:uint = 0;
            var lli:LinkedListIterator = _commands.iterator() as LinkedListIterator;
            for (var i:uint=0; i<index && lli.hasNext(); i++)
            {
                lli.next();
                if (regExp.test(lli.current as String))
                    count++;
            }
            return count;
        }*/
        
 /*       private function parseLine(orig:String, lineNum:uint):Boolean
        {
            var p:ParagraphElement = this.textFlow.getChildAt(lineNum) as ParagraphElement;
            var s:SpanElement = p.getChildAt(0) as SpanElement;
            
            var pass:Boolean = false;
            try
            {
                if (parseNode(orig, s.text, countValidLines(StructureNames.NODE, lineNum)))
                    pass = true;
            } catch (error:Error) {
                pass = false;
                Alert.show(error.message);
            }
            if (pass)
                s.setStyle("color", 0x00ff00);
            else
                s.setStyle("color", 0xff0000);
            return pass;
            
            
            
        }*/
        
        /*private function parseNode(newLine:String):Boolean
        {
            var oldMatch:Boolean = nodeReg.test(oldLine);
            var newMatch:Boolean = nodeReg.test(newLine);
            
            if (!oldMatch && !newMatch)
                return false;
            
            var oldResult:Array = trimAndCompact(oldLine);
            var newResult:Array = trimAndCompact(newLine);
            
            if (oldMatch && !newMatch)
            {
                _structure.removeNodeByName(oldResult[1]);
                return true;
            }
            if (!oldMatch && newMatch)
            {
                _structure.addNodeAt(newResult[1], newResult[2], newResult[3], linesBefore);
                return true;
            }
            if (oldMatch && newMatch)
            {
                _structure.removeNodeByName(oldResult[1]);
                _structure.addNodeAt(newResult[1], newResult[2], newResult[3], linesBefore);
                
                return true;
            }
            return false;
        }*/
        
        /**
        * Spilts a String into small Strings by delimiter ' ', ',', '(', ')' and
        * clear space at the begining or the end of the small Strings.
        */ 
        private function trimAndCompact(line:String):Array
        {
            var result:Array = new Array();
            if (null == line)
                return result;
            
            var temp:Array = line.split(/[ ,\\(\\)]/);
            
            for (var i:uint=0; i<temp.length; i++)
            {
                if (temp[i] != "")
                    result.push(trim(temp[i] as String));
            }
            
            for (var j:uint=0; j<result.length; j++)
            {
                trace(result[j]);
            }
            return result;
        }
        
        /**
        * Removes every space in a String and returns the new String without
        * modifying the original one.
        */
        private function trim(str:String):String
        {
            var buf:StringBuffer = new StringBuffer();
            if (null == str)return buf.toString();
            
            for (var i:uint=0; i<str.length; i++)
            {
                if (str.charAt(i) != " ")
                    buf.append(str.charAt(i));
            }
            return buf.toString();
        }
        
 /*       private function debugPrintCommands():void
        {
            trace("[Commands]");
            trace("size: " + _commands.size);
            var lli : ILinkedListIterator = _commands.iterator() as ILinkedListIterator;
            for (var i:uint=0; lli.hasNext(); i++) {
                lli.next();
                trace (i + ": [" + lli.current + "]");
            }
            trace();
        }*/
    }
}